import { Memory } from "./memory";
import type { StringGanarator, TornaDocDetail, TornaParamInfo, TsTypeInfo } from "./types/type";

export interface TsFileGenaratorOptions {
  genaratorVersion?: string;

  queryTypeName?: string
  queryDescription?: string

  bodyTypeName?: string
  bodyDescription?: string

  respTypeName?: string
  respDescription?: string

  headerTypeName?: string
  headerDescription?: string

  queryAllowString?: string

  toInterface?: string
}

export const TYPENAME_QUERY_PARAMS = 'QueryParams';
export const TYPENAME_BODY_PARAMS = 'BodyParams';
export const TYPENAME_HEADER_PARAMS = 'HeaderParams';
export const TYPENAME_RESPONSE = 'Response';
export const DEFAULT_PART_PARAMS = {
  createMode: 0,
  creatorName: "",
  docId: "",
  enumId: "",
  example: "",
  global: false,
  gmtCreate: "",
  gmtModified: "",
  isDeleted: 0,
  maxLength: "",
  modifierName: "",
  modifyMode: 0,
  orderIndex: 0,
  parentId: "",
  required: 0,
  style: 0,
  type: ""
};
export const API_QUERY_PARAMS: TornaParamInfo = {
  id: TYPENAME_QUERY_PARAMS,
  name: TYPENAME_QUERY_PARAMS,
  description: 'REQUEST: url searh qurey参数',
  ...DEFAULT_PART_PARAMS,
};
export const API_BODY_PARAMS: TornaParamInfo = {
  id: TYPENAME_BODY_PARAMS,
  name: TYPENAME_BODY_PARAMS,
  description: 'REQUEST: post body请求参数',
  ...DEFAULT_PART_PARAMS,
};
export const API_HEADER_PARAMS: TornaParamInfo = {
  id: TYPENAME_HEADER_PARAMS,
  name: TYPENAME_HEADER_PARAMS,
  description: 'REQUEST: 请求的header',
  ...DEFAULT_PART_PARAMS,
};
export const API_RESPONSE: TornaParamInfo = {
  id: TYPENAME_RESPONSE,
  name: TYPENAME_RESPONSE,
  description: 'RESPONSE: 接口返回',
  ...DEFAULT_PART_PARAMS,
};

export const TAB_STR = '  ';
export const REG_FIELD_NAME_2_TYPE_NAME = /((DTO|VO)(List)|(List))*$/;
export const firstLetterUpperCase = (str: string) => str[0].toUpperCase() + str.substring(1);
export const spaceLetterUpperCase = (str: string) => str.replace(/\s+(\S)/g, (_0, _1) => _1.toUpperCase());
export class TsFileGenarator {

  paramInfos: { [key in string]?: TornaParamInfo } = {};
  tsTypeInfos: { [key in string]?: TsTypeInfo } = {};
  opts: TsFileGenaratorOptions | undefined;

  get headerParams() { return this.tsTypeInfos[TYPENAME_HEADER_PARAMS]; }
  get queryParams() { return this.tsTypeInfos[TYPENAME_QUERY_PARAMS]; }
  get bodyParams() { return this.tsTypeInfos[TYPENAME_BODY_PARAMS]; }
  get response() { return this.tsTypeInfos[TYPENAME_RESPONSE]; }

  private tsTypeIds: string[] = [];
  private usedTypeNames: { [x in string]: true } = {};

  genTypeName: StringGanarator<TornaParamInfo> = v => {
    const parent = this.paramInfos[v.parentId];
    let tn = firstLetterUpperCase(v.name.trim());
    tn = spaceLetterUpperCase(tn);
    tn = tn.replace(REG_FIELD_NAME_2_TYPE_NAME, "");
    if (parent && parent?.id === this.response?.node.id) {
      tn = 'Resp' + tn;
    }
    while (this.usedTypeNames[tn]) {
      tn = tn.replace(/_?(\d+)?$/, (_, num) => '_' + (Number(num || 0) + 1));
    }
    this.usedTypeNames[tn] = true;
    return tn;
  };
  genFieldBody: StringGanarator<TornaParamInfo> = v => {
    const allowStr = v.parentId === TYPENAME_QUERY_PARAMS && this.opts?.queryAllowString !== 'false';
    const nameStr = v.name.match(/\s|#|\?/) ? `"${v.name}"` : v.name;
    let ret = TAB_STR + nameStr + (v.required ? ': ' : '?: ');
    switch (v.type) {
      case 'boolean':
        ret += 'boolean' + (allowStr ? ' | string' : ' ');
        break;
      case 'date':
      case 'string':
        ret += 'string';
        break;
      case 'byte':
      case 'char':
      case 'int':
      case 'integer':
      case 'int8':
      case 'int16':
      case 'int32':
      case 'int64':
      case 'short':
      case 'long':
      case 'float':
      case 'double':
      case 'bigdecimal':
        ret += 'number' + (allowStr ? ' | string' : ' ');
        break;
      case 'enum':
        if (typeof v.example === 'string') { ret += JSON.stringify(v.example) + " | string"; }
        else if (typeof v.example === 'number') { ret += v.example + " | number" + (allowStr ? ' | string' : ' '); }
        else { ret += "any"; }
        break;
      case 'map':
        ret += "{ [key in symbol | string | number]?: any }" + (allowStr ? ' | string' : ' ');
        break;
      case 'object':
        ret += this.tsTypeInfos[v.id]?.typeName;
        break;
      case 'array':
        if (v.example?.match(/^.*?,.*?$/)) { ret += 'string[]' + (allowStr ? ' | string' : ' '); }
        else { ret += this.tsTypeInfos[v.id]?.typeName + '[]' + (allowStr ? ' | string' : ' '); }
        break;
      case 'number[]':
      case 'string[]':
      case 'boolean[]':
        ret += v.type + (allowStr ? ' | string' : ' ');
        break;
      default:
        ret += (v.type || 'unknown') + (allowStr ? ' | string' : ' ');
    }

    return ret;
  };

  genFieldHead: StringGanarator<TornaParamInfo> = v => `${TAB_STR}/** @desc ${v.type}, ${v.description} */\n`;

  genFieldTail: StringGanarator<TornaParamInfo> = v => `\n\n`;

  genTypeBody: StringGanarator<TsTypeInfo> = v => {
    if (!v.fieldBodies.length) {
      return this.opts?.toInterface === 'true' ?
        `export interface ${v.typeName} { [x: string]: any; }` :
        `export type ${v.typeName} = { [x: string]: any; }`;
    }

    let str = this.opts?.toInterface === 'true' ?
      `export interface ${v.typeName} {` + '\n' :
      `export type ${v.typeName} = {` + '\n';
    for (let i = 0; i < v.fieldBodies.length; ++i) {
      str += v.fieldHeads[i];
      str += v.fieldBodies[i];
      str += v.fieldTails[i];
    }
    str += '}';
    return str;
  };

  genTypeHead: StringGanarator<TsTypeInfo> = v => {
    const node = v.node;
    let ret = "/**";
    ret += "\n * @desc " + node.description;
    const parent = v.parent && v.parent();
    if (parent) {
      ret += "\n * @see {" + parent.typeName + "} Referenced by " + parent.typeName;
    }
    ret += "\n */";
    return ret;
  };

  init(opts?: TsFileGenaratorOptions) {
    this.opts = opts;
    this.paramInfos = {
      [TYPENAME_QUERY_PARAMS]: {
        ...API_QUERY_PARAMS,
        name: opts?.queryTypeName || API_QUERY_PARAMS.name,
        description: opts?.queryDescription || API_QUERY_PARAMS.description
      }, [TYPENAME_BODY_PARAMS]: {
        ...API_BODY_PARAMS,
        name: opts?.bodyTypeName || API_BODY_PARAMS.name,
        description: opts?.bodyDescription || API_BODY_PARAMS.description
      }, [TYPENAME_RESPONSE]: {
        ...API_RESPONSE,
        name: opts?.respTypeName || API_RESPONSE.name,
        description: opts?.respDescription || API_RESPONSE.description
      }, [TYPENAME_HEADER_PARAMS]: {
        ...API_HEADER_PARAMS,
        name: opts?.headerTypeName || API_HEADER_PARAMS.name,
        description: opts?.headerDescription || API_HEADER_PARAMS.description
      },
    };
    const fillTypeInfo = () => ({
      parent: () => undefined,
      fieldBodies: [],
      fieldTails: [],
      fieldHeads: [],
    });
    this.tsTypeInfos = {
      [TYPENAME_QUERY_PARAMS]: {
        node: this.paramInfos[TYPENAME_QUERY_PARAMS]!,
        typeName: this.genTypeName(this.paramInfos[TYPENAME_QUERY_PARAMS]!),
        ...fillTypeInfo(),
      }, [TYPENAME_BODY_PARAMS]: {
        node: this.paramInfos[TYPENAME_BODY_PARAMS]!,
        typeName: this.genTypeName(this.paramInfos[TYPENAME_BODY_PARAMS]!),
        ...fillTypeInfo(),
      }, [TYPENAME_RESPONSE]: {
        node: this.paramInfos[TYPENAME_RESPONSE]!,
        typeName: this.genTypeName(this.paramInfos[TYPENAME_RESPONSE]!),
        ...fillTypeInfo(),
      }, [TYPENAME_HEADER_PARAMS]: {
        node: this.paramInfos[TYPENAME_HEADER_PARAMS]!,
        typeName: this.genTypeName(this.paramInfos[TYPENAME_HEADER_PARAMS]!),
        ...fillTypeInfo(),
      }
    };
    return this;
  }

  private flat(list: TornaParamInfo[], rootId: string) {

    // 预处理
    for (let i = 0; i < list.length; ++i) {

      list[i].name = list[i].name.replace('-', '_');
      list[i].parentId = list[i].parentId || rootId;
      list[i].type = list[i].type.toLowerCase();

      const { type, example } = list[i];
      if (type === 'array' || type === 'list') {
        // 在torna中，基础类型的数组只能通过example判断。
        if (example?.match(/^(true|false),(true|false)$/)) {
          list[i].type = 'boolean[]';
        } else if (example?.match(/^(-?\d+)(\.\d+)?,(-?\d+)(\.\d+)?$/)) {
          list[i].type = 'number[]';
        } else if (example?.match(/^.*?,.*?$/)) {
          list[i].type = 'string[]';
        } else {
          list[i].type = 'array';
        }
      }

      if (list[i].type === 'object' || list[i].type === 'array') {
        const { id, parentId } = list[i];
        this.paramInfos[id] = list[i];
        this.tsTypeIds.push(id);
        this.tsTypeInfos[id] = {
          node: list[i],
          parent: () => this.tsTypeInfos[parentId],
          typeName: this.genTypeName(list[i]),
          fieldHeads: [],
          fieldBodies: [],
          fieldTails: [],
        };
      }
    }

    for (const key in this.tsTypeInfos) {
      const tsTypeInfo = this.tsTypeInfos[key];
      if (!tsTypeInfo) { continue; }

      for (let i = 0; i < list.length; ++i) {
        const rawInfo = list[i];
        if (rawInfo.parentId !== tsTypeInfo.node.id) { continue; }
        const fieldHead = this.genFieldHead(rawInfo);
        const fieldBody = this.genFieldBody(rawInfo);
        const fieldTail = this.genFieldTail(rawInfo);
        tsTypeInfo.fieldHeads.push(fieldHead);
        tsTypeInfo.fieldBodies.push(fieldBody);
        tsTypeInfo.fieldTails.push(fieldTail);
      }
    }
  }

  docNamePath(docInfo: TornaDocDetail, visitor: Memory.IUser): string[] {
    const project = Memory.find('project', visitor, docInfo.projectId);
    const module = Memory.find('module', visitor, docInfo.moduleId);
    let ret = [];
    if (project) { ret.push(project.name); }
    if (module) { ret.push(module.name); }
    ret.push(docInfo.name);
    return ret;
  }
  typescriptNamespaceDocCommentStr(docInfo: TornaDocDetail, visitor: Memory.IUser): string {
    const envLines = () => {
      let ret = '';
      docInfo.debugEnvs.forEach(v => {
        ret += `\n *               [${v.name}] ${v.url}`;
      });
      return ret;
    };
    const summary = this.docNamePath(docInfo, visitor).join('/');
    return `/**
 * Generated by Torna Api Visitor ${this.opts?.genaratorVersion} DO NOT EDIT IT DIRECTLY!
 * 
 * @summary      ${summary}
 * @author       ${docInfo.author}
 * @see          ${visitor.url}/#/view/${docInfo.id}
 * @apiUrl       ${docInfo.url}
 * 
 * @projectId    ${docInfo.projectId}
 * @moduleId     ${docInfo.moduleId}
 * @docId        ${docInfo.id}
 * @docUrl       ${visitor.url}
 * @baseUrl      ${docInfo.baseUrl}
 * @envs         ${envLines()}
 * @httpMethod   ${docInfo.httpMethod}
 * @description  ${docInfo.description.replace(/\n/g, '\n////               ')}
 * @createTime   ${docInfo.gmtCreate}
 * @lastModified ${docInfo.gmtModified}
 */
`;
  }

  headerStr(docInfo: TornaDocDetail, visitor: Memory.IUser): string {
    const envLines = () => {
      let ret = '';
      docInfo.debugEnvs.forEach(v => {
        ret += `\n////               [${v.name}] ${v.url}`;
      });
      return ret;
    };

    const summary = this.docNamePath(docInfo, visitor).join('/');
    return `//// Generated by Torna Api Visitor ${this.opts?.genaratorVersion} DO NOT EDIT IT DIRECTLY!
//// @name         ${summary}
//// @apiUrl       ${docInfo.url}

//// @author       ${docInfo.author}
//// @see          ${visitor.url}/#/view/${docInfo.id}
//// @projectId    ${docInfo.projectId}
//// @moduleId     ${docInfo.moduleId}
//// @docId        ${docInfo.id}
//// @docUrl       ${visitor.url}
//// @baseUrl      ${docInfo.baseUrl}
//// @envs         ${envLines()}
//// @httpMethod   ${docInfo.httpMethod}
//// @description  ${docInfo.description.replace(/\n/g, '\n////               ')}
//// @createTime   ${docInfo.gmtCreate}
//// @lastModified ${docInfo.gmtModified} 
`;
  }

  typeDeclarationsStr(docInfo: TornaDocDetail, visitor: Memory.IUser): string {
    this.tsTypeIds = [];
    let str = '';

    if (docInfo.headerParams.length) {
      this.tsTypeIds.push(TYPENAME_HEADER_PARAMS);
      this.flat(docInfo.headerParams, TYPENAME_HEADER_PARAMS);
    }

    if (docInfo.queryParams.length) {
      this.tsTypeIds.push(TYPENAME_QUERY_PARAMS);
      this.flat(docInfo.queryParams, TYPENAME_QUERY_PARAMS);
    }

    if (docInfo.requestParams.length) {
      this.tsTypeIds.push(TYPENAME_BODY_PARAMS);
      this.flat(docInfo.requestParams, TYPENAME_BODY_PARAMS);
    }

    if (docInfo.responseParams.length) {
      this.tsTypeIds.push(TYPENAME_RESPONSE);
      this.flat(docInfo.responseParams, TYPENAME_RESPONSE);
    }
    this.tsTypeIds = Array.from(new Set(this.tsTypeIds));

    for (const idx in this.tsTypeIds) {
      const key = this.tsTypeIds[idx];
      const typeInfo = this.tsTypeInfos[key];
      if (!typeInfo) { continue; }

      const typeBody = this.genTypeBody(typeInfo);
      str += this.genTypeHead(typeInfo) + '\n';
      str += typeBody + '\n\n';
    }

    return str;
  }

  genarate(docInfo: TornaDocDetail, visitor: Memory.IUser) {
    return {
      headerStr: this.headerStr(docInfo, visitor),
      typeDeclarationsStr: this.typeDeclarationsStr(docInfo, visitor),
      namespaceDocCommentStr: this.typescriptNamespaceDocCommentStr(docInfo, visitor),

    };
  }
}